In [2]:
library('keras')
library('tensorflow')
library('tidyverse')
library('fs')
library('tfdatasets')
library('fields')
library('magick')

Wczytanie danych¶

Ten kod wykonuje następujące czynności: najpierw definiuje funkcję convert_to_supported_format, która konwertuje obrazy do określonego formatu. Funkcja ta odczytuje obraz z podanej ścieżki, następnie tworzy nową ścieżkę dla przekonwertowanego obrazu z odpowiednim rozszerzeniem formatu. Następnie zapisuje przekonwertowany obraz do nowej lokalizacji i zwraca ścieżkę do nowego obrazu.

Dalej kod kopiuje pliki obrazowe do nowego katalogu "Image" na podstawie listy plików image_files_copy z oryginalnej lokalizacji /kaggle/input/flood-area-segmentation/Image. Następnie lista plików obrazowych jest tworzona w nowym katalogu "Image". Ostatnią operacją jest konwersja wszystkich obrazów w nowej lokalizacji za pomocą funkcji convert_to_supported_format i zapisanie wynikowych ścieżek do zmiennej converted_images za pomocą funkcji sapply.

In [3]:
#Przekształcenie formatu
convert_to_supported_format <- function(file_path, output_format = "jpeg") {
  img <- image_read(file_path)
  output_path <- sub("\\.[a-z]+$", paste0(".", output_format), file_path)
  image_write(img, path = output_path, format = output_format)
  return(output_path)
}

image_files_copy <- list.files(path = "/kaggle/input/flood-area-segmentation/Image", full.names = TRUE)

dir.create(file.path("Image"), recursive = TRUE, showWarnings = FALSE)
file.copy(image_files_copy, file.path("Image"))
image_files <- list.files(path = "/kaggle/working/Image", full.names = TRUE)
converted_images <- sapply(image_files, convert_to_supported_format)
  1. TRUE
  2. TRUE
  3. TRUE
  4. TRUE
  5. TRUE
  6. TRUE
  7. TRUE
  8. TRUE
  9. TRUE
  10. TRUE
  11. TRUE
  12. TRUE
  13. TRUE
  14. TRUE
  15. TRUE
  16. TRUE
  17. TRUE
  18. TRUE
  19. TRUE
  20. TRUE
  21. TRUE
  22. TRUE
  23. TRUE
  24. TRUE
  25. TRUE
  26. TRUE
  27. TRUE
  28. TRUE
  29. TRUE
  30. TRUE
  31. TRUE
  32. TRUE
  33. TRUE
  34. TRUE
  35. TRUE
  36. TRUE
  37. TRUE
  38. TRUE
  39. TRUE
  40. TRUE
  41. TRUE
  42. TRUE
  43. TRUE
  44. TRUE
  45. TRUE
  46. TRUE
  47. TRUE
  48. TRUE
  49. TRUE
  50. TRUE
  51. TRUE
  52. TRUE
  53. TRUE
  54. TRUE
  55. TRUE
  56. TRUE
  57. TRUE
  58. TRUE
  59. TRUE
  60. TRUE
  61. TRUE
  62. TRUE
  63. TRUE
  64. TRUE
  65. TRUE
  66. TRUE
  67. TRUE
  68. TRUE
  69. TRUE
  70. TRUE
  71. TRUE
  72. TRUE
  73. TRUE
  74. TRUE
  75. TRUE
  76. TRUE
  77. TRUE
  78. TRUE
  79. TRUE
  80. TRUE
  81. TRUE
  82. TRUE
  83. TRUE
  84. TRUE
  85. TRUE
  86. TRUE
  87. TRUE
  88. TRUE
  89. TRUE
  90. TRUE
  91. TRUE
  92. TRUE
  93. TRUE
  94. TRUE
  95. TRUE
  96. TRUE
  97. TRUE
  98. TRUE
  99. TRUE
  100. TRUE
  101. TRUE
  102. TRUE
  103. TRUE
  104. TRUE
  105. TRUE
  106. TRUE
  107. TRUE
  108. TRUE
  109. TRUE
  110. TRUE
  111. TRUE
  112. TRUE
  113. TRUE
  114. TRUE
  115. TRUE
  116. TRUE
  117. TRUE
  118. TRUE
  119. TRUE
  120. TRUE
  121. TRUE
  122. TRUE
  123. TRUE
  124. TRUE
  125. TRUE
  126. TRUE
  127. TRUE
  128. TRUE
  129. TRUE
  130. TRUE
  131. TRUE
  132. TRUE
  133. TRUE
  134. TRUE
  135. TRUE
  136. TRUE
  137. TRUE
  138. TRUE
  139. TRUE
  140. TRUE
  141. TRUE
  142. TRUE
  143. TRUE
  144. TRUE
  145. TRUE
  146. TRUE
  147. TRUE
  148. TRUE
  149. TRUE
  150. TRUE
  151. TRUE
  152. TRUE
  153. TRUE
  154. TRUE
  155. TRUE
  156. TRUE
  157. TRUE
  158. TRUE
  159. TRUE
  160. TRUE
  161. TRUE
  162. TRUE
  163. TRUE
  164. TRUE
  165. TRUE
  166. TRUE
  167. TRUE
  168. TRUE
  169. TRUE
  170. TRUE
  171. TRUE
  172. TRUE
  173. TRUE
  174. TRUE
  175. TRUE
  176. TRUE
  177. TRUE
  178. TRUE
  179. TRUE
  180. TRUE
  181. TRUE
  182. TRUE
  183. TRUE
  184. TRUE
  185. TRUE
  186. TRUE
  187. TRUE
  188. TRUE
  189. TRUE
  190. TRUE
  191. TRUE
  192. TRUE
  193. TRUE
  194. TRUE
  195. TRUE
  196. TRUE
  197. TRUE
  198. TRUE
  199. TRUE
  200. TRUE
  201. TRUE
  202. TRUE
  203. TRUE
  204. TRUE
  205. TRUE
  206. TRUE
  207. TRUE
  208. TRUE
  209. TRUE
  210. TRUE
  211. TRUE
  212. TRUE
  213. TRUE
  214. TRUE
  215. TRUE
  216. TRUE
  217. TRUE
  218. TRUE
  219. TRUE
  220. TRUE
  221. TRUE
  222. TRUE
  223. TRUE
  224. TRUE
  225. TRUE
  226. TRUE
  227. TRUE
  228. TRUE
  229. TRUE
  230. TRUE
  231. TRUE
  232. TRUE
  233. TRUE
  234. TRUE
  235. TRUE
  236. TRUE
  237. TRUE
  238. TRUE
  239. TRUE
  240. TRUE
  241. TRUE
  242. TRUE
  243. TRUE
  244. TRUE
  245. TRUE
  246. TRUE
  247. TRUE
  248. TRUE
  249. TRUE
  250. TRUE
  251. TRUE
  252. TRUE
  253. TRUE
  254. TRUE
  255. TRUE
  256. TRUE
  257. TRUE
  258. TRUE
  259. TRUE
  260. TRUE
  261. TRUE
  262. TRUE
  263. TRUE
  264. TRUE
  265. TRUE
  266. TRUE
  267. TRUE
  268. TRUE
  269. TRUE
  270. TRUE
  271. TRUE
  272. TRUE
  273. TRUE
  274. TRUE
  275. TRUE
  276. TRUE
  277. TRUE
  278. TRUE
  279. TRUE
  280. TRUE
  281. TRUE
  282. TRUE
  283. TRUE
  284. TRUE
  285. TRUE
  286. TRUE
  287. TRUE
  288. TRUE
  289. TRUE
  290. TRUE
In [4]:
input_dir <- "/kaggle/working/Image"
target_dir <- "/kaggle/input/flood-area-segmentation/Mask"
In [5]:
image_paths = tibble(input = dir_ls(input_dir, glob = "*.jpeg"),
       target = dir_ls(target_dir, glob = "*.png"))
In [6]:
par(mfrow = c(1, 2))
#Imput image
display_image_tensor <- function(x, ..., max = 255,
                                 plot_margins = c(0, 0, 0, 0)) {   
  if(!is.null(plot_margins))
    par(mar = plot_margins)
 
  x %>%
    as.array() %>%
    drop() %>%
    as.raster(max = max) %>%
    plot(..., interpolate = FALSE)
}


image_tensor <- image_paths$input[20] %>% 
   tf$io$read_file() %>%
   tf$io$decode_jpeg()

str(image_tensor)
display_image_tensor(image_tensor)

#Target mask
display_target_tensor <- function(target) display_image_tensor(target)   

target <- image_paths$target[20] %>%
   tf$io$read_file() %>%
   tf$io$decode_png()
str(target)
display_target_tensor(target)
<tf.Tensor: shape=(443, 760, 3), dtype=uint8, numpy=…>
<tf.Tensor: shape=(443, 760, 1), dtype=uint8, numpy=…>

Przygotowanie danych¶

Przygotowanie danych rozpoczyna się od funkcji, która odczytuje i dekoduje obrazy z plików, umożliwiając opcjonalne przeskalowanie ich do określonych wymiarów. Następnie funkcja "make_dataset" wykorzystuje tę funkcję do stworzenia zbioru danych, który zawiera znormalizowane obrazy wejściowe i docelowe (np. obrazy i ich etykiety dla problemów segmentacji). Kolejne kroki obejmują normalizację wartości pikseli oraz organizację danych w paczki (batche), co jest kluczowe dla efektywnego uczenia modelu. Cały proces ma na celu przygotowanie danych w formacie odpowiednim do dalszego wykorzystania w treningu modelu, zapewniając ich spójność i gotowość do analizy przez algorytmy uczenia maszynowego.

In [7]:
tf_read_image <- function(path, format = "image", resize = NULL, ...) {
  img <- path %>%
    tf$io$read_file() %>%
    tf$io[[paste0("decode_", format)]](...)
  
  if (!is.null(resize)) {
    img <- img %>%
      tf$image$resize(as.integer(resize))
  }
  img
}

img_size <- c(256, 256)

tf_read_image_and_resize <- function(..., resize = img_size) {
  tf_read_image(..., resize = resize)
}

# Funkcja do tworzenia datasetu
make_dataset <- function(paths_df) {
  tensor_slices_dataset(paths_df) %>%
    dataset_map(function(path) {
      image <- path$input %>%
        tf_read_image_and_resize("jpeg", channels = 3L)
      target <- path$target %>%
        tf_read_image_and_resize("png", channels = 1L)
      
      # Normalizacja obrazów
      image <- image / 255
      target <- target / 255
      
      list(image, target)
    }) %>%
    dataset_cache() %>%
    dataset_shuffle(buffer_size = nrow(paths_df)) %>%
    dataset_batch(32)
}

Podzial zbioru¶

Zbiory danych są podzielone: 70% danych trafia do zbioru treningowego (train_paths), a pozostałe 30% jest przypisane do zbioru testowego (test_paths). Z zbioru treningowego losowo wybierane jest dodatkowo 15% danych, które tworzą zbiór walidacyjny (vali_paths). Każdy ze zbiorów jest następnie przekształcony za pomocą funkcji make_dataset.

In [8]:
set.seed(20)
train_idx = sample(1:nrow(image_paths), nrow(image_paths)*0.70)

train_paths <- image_paths[train_idx, ]
test_paths <- image_paths[-train_idx, ]
vali_paths = train_paths %>% sample_n(size = nrow(image_paths)*0.15)


train_dataset <- make_dataset(train_paths)
vali_dataset <- make_dataset(vali_paths)
test_dataset <- make_dataset(test_paths)

U-NET¶

Opis architektury U-Net

  1. Warstwa wejściowa (Input): warstwa która przyjmuje obrazy o określonym rozmiarze input_size.
  2. Koder (Encoder): Encoder Block 1: Pierwszy blok kodera składa się z dwóch warstw konwolucyjnych z funkcją aktywacji ReLU, zastosowaną w celu ekstrakcji cech z wejściowych obrazów. Po każdej warstwie konwolucyjnej znajduje się warstwa dropout z współczynnikiem 0.1 w celu redukcji overfittingu. Następnie jest warstwa Max Pooling z rozmiarem 2x2, która zmniejsza rozmiar przestrzenny danych wejściowych, co zwiększa efektywność obliczeniową i pomaga w ekstrakcji najważniejszych cech. Encoder Block 2, 3, 4: Kolejne bloki kodera powtarzają ten sam schemat, zwiększając liczbę filtrów (num_filters) w kolejnych blokach, co pozwala na wykrywanie coraz bardziej abstrakcyjnych cech.
  3. Most (Bridge) : Po osiągnięciu najniższego poziomu rozdzielczości przestrzennej, dane przechodzą przez blok mostu, który również składa się z dwóch warstw konwolucyjnych z aktywacją ReLU. Ten fragment sieci służy do łączenia szczegółowych informacji z kodera przed przejściem do dekodera.
  4. Dekoder (Decoder): Decoder Block 1, 2, 3, 4: Proces dekodowania rozpoczyna się od bloku dekodera, w którym używana jest warstwa konwolucji transponowanej, aby zwiększyć rozmiar przestrzenny danych. Następnie informacje są łączone z odpowiednimi połączeniami pomostowymi (skip connections) z kodera, co pomaga w przywróceniu szczegółów do obrazu. Każdy blok dekodera składa się z dwóch warstw konwolucyjnych z funkcją aktywacji ReLU i warstwy dropout.
  5. Warstwa wyjściowa (Output): Ostatnią warstwą sieci jest warstwa konwolucyjna z jednym filtrem i funkcją aktywacji sigmoidalną, która zwraca przewidywania segmentacji na obrazie. Warstwa ta generuje mapę binarną, gdzie każdy piksel obrazu wejściowego jest przyporządkowany do klasy (np. przedmiotu zainteresowania kontra tło).
In [9]:
conv_block <- function(inputs, num_filters) {
  x <- layer_conv_2d(inputs, filters = num_filters, kernel_size = c(3, 3), activation = "relu", padding = "same")
  x <- layer_dropout(x, rate = 0.1)
  x <- layer_conv_2d(x, filters = num_filters, kernel_size = c(3, 3), activation = "relu", padding = "same")
  return(x)
}

encoder_block <- function(input, num_filters) {
  x <- conv_block(input, num_filters)
  p <- layer_max_pooling_2d(x, pool_size = c(2, 2))
  return(list(x, p))
}

decoder_block <- function(input, skip_features, num_filters) {
  x <- layer_conv_2d_transpose(input, filters = num_filters, kernel_size = c(2, 2), strides = c(2, 2), padding = "same")
  x <- layer_concatenate(list(x, skip_features))
  x <- conv_block(x, num_filters)
  return(x)
}

UNet <- function(input_size) {
  input_layer_nodes <- 16
  input <- layer_input(shape = input_size)

  # Encoder
  encoder1 <- encoder_block(input, input_layer_nodes * 1)
  s1 <- encoder1[[1]]
  p1 <- encoder1[[2]]

  encoder2 <- encoder_block(p1, input_layer_nodes * 2)
  s2 <- encoder2[[1]]
  p2 <- encoder2[[2]]

  encoder3 <- encoder_block(p2, input_layer_nodes * 4)
  s3 <- encoder3[[1]]
  p3 <- encoder3[[2]]

  encoder4 <- encoder_block(p3, input_layer_nodes * 8)
  s4 <- encoder4[[1]]
  p4 <- encoder4[[2]]

  # Bridge
  b1 <- conv_block(p4, input_layer_nodes * 16)

  # Decoder
  d1 <- decoder_block(b1, s4, input_layer_nodes * 8)
  d2 <- decoder_block(d1, s3, input_layer_nodes * 4)
  d3 <- decoder_block(d2, s2, input_layer_nodes * 2)
  d4 <- decoder_block(d3, s1, input_layer_nodes * 1)

  output <- layer_conv_2d(d4, filters = 1, kernel_size = c(1, 1), padding = "same", activation = "sigmoid")

  model <- keras_model(inputs = input, outputs = output, name = "U-Net")
  return(model)
}

Intersection over Union (IoU) / Jaccard Index

IoU jest jedną z najczęściej stosowanych metryk do oceny segmentacji. Jest to stosunek przecięcia do sumy predykcji i rzeczywistej maski.

In [10]:
iou_metric <- custom_metric("iou", function(y_true, y_pred) {
  y_pred <- k_round(y_pred)
  intersection <- k_sum(y_true * y_pred)
  sum <- k_sum(y_true + y_pred)
  smooth <- 1e-6
  (intersection + smooth) / (sum - intersection + smooth)
})

Funkcja aktywacji sigmoid w warstwie wyjściowej modelu oraz binary_crossentropy jako funkcja straty są używane wspólnie w celu skutecznego uczenia modelu do dokładnego segmentowania obrazów na dwie klasy: obszary zalanego oraz ziemi.

In [11]:
unet = UNet(c(img_size, 3))
unet %>% compile(optimizer="adam", loss='binary_crossentropy', metrics=c(iou_metric, 'accuracy'))
summary(unet)
Model: "U-Net"
________________________________________________________________________________
 Layer (type)             Output Shape      Param #  Connected to               
================================================================================
 input_1 (InputLayer)     [(None, 256, 256  0        []                         
                          , 3)]                                                 
 conv2d (Conv2D)          (None, 256, 256,  448      ['input_1[0][0]']          
                           16)                                                  
 dropout (Dropout)        (None, 256, 256,  0        ['conv2d[0][0]']           
                           16)                                                  
 conv2d_1 (Conv2D)        (None, 256, 256,  2320     ['dropout[0][0]']          
                           16)                                                  
 max_pooling2d (MaxPoolin  (None, 128, 128,  0       ['conv2d_1[0][0]']         
 g2D)                      16)                                                  
 conv2d_2 (Conv2D)        (None, 128, 128,  4640     ['max_pooling2d[0][0]']    
                           32)                                                  
 dropout_1 (Dropout)      (None, 128, 128,  0        ['conv2d_2[0][0]']         
                           32)                                                  
 conv2d_3 (Conv2D)        (None, 128, 128,  9248     ['dropout_1[0][0]']        
                           32)                                                  
 max_pooling2d_1 (MaxPool  (None, 64, 64, 3  0       ['conv2d_3[0][0]']         
 ing2D)                   2)                                                    
 conv2d_4 (Conv2D)        (None, 64, 64, 6  18496    ['max_pooling2d_1[0][0]']  
                          4)                                                    
 dropout_2 (Dropout)      (None, 64, 64, 6  0        ['conv2d_4[0][0]']         
                          4)                                                    
 conv2d_5 (Conv2D)        (None, 64, 64, 6  36928    ['dropout_2[0][0]']        
                          4)                                                    
 max_pooling2d_2 (MaxPool  (None, 32, 32, 6  0       ['conv2d_5[0][0]']         
 ing2D)                   4)                                                    
 conv2d_6 (Conv2D)        (None, 32, 32, 1  73856    ['max_pooling2d_2[0][0]']  
                          28)                                                   
 dropout_3 (Dropout)      (None, 32, 32, 1  0        ['conv2d_6[0][0]']         
                          28)                                                   
 conv2d_7 (Conv2D)        (None, 32, 32, 1  147584   ['dropout_3[0][0]']        
                          28)                                                   
 max_pooling2d_3 (MaxPool  (None, 16, 16, 1  0       ['conv2d_7[0][0]']         
 ing2D)                   28)                                                   
 conv2d_8 (Conv2D)        (None, 16, 16, 2  295168   ['max_pooling2d_3[0][0]']  
                          56)                                                   
 dropout_4 (Dropout)      (None, 16, 16, 2  0        ['conv2d_8[0][0]']         
                          56)                                                   
 conv2d_9 (Conv2D)        (None, 16, 16, 2  590080   ['dropout_4[0][0]']        
                          56)                                                   
 conv2d_transpose (Conv2D  (None, 32, 32, 1  131200  ['conv2d_9[0][0]']         
 Transpose)               28)                                                   
 concatenate (Concatenate  (None, 32, 32, 2  0       ['conv2d_transpose[0][0]', 
 )                        56)                         'conv2d_7[0][0]']         
 conv2d_10 (Conv2D)       (None, 32, 32, 1  295040   ['concatenate[0][0]']      
                          28)                                                   
 dropout_5 (Dropout)      (None, 32, 32, 1  0        ['conv2d_10[0][0]']        
                          28)                                                   
 conv2d_11 (Conv2D)       (None, 32, 32, 1  147584   ['dropout_5[0][0]']        
                          28)                                                   
 conv2d_transpose_1 (Conv  (None, 64, 64, 6  32832   ['conv2d_11[0][0]']        
 2DTranspose)             4)                                                    
 concatenate_1 (Concatena  (None, 64, 64, 1  0       ['conv2d_transpose_1[0][0]'
 te)                      28)                        , 'conv2d_5[0][0]']        
 conv2d_12 (Conv2D)       (None, 64, 64, 6  73792    ['concatenate_1[0][0]']    
                          4)                                                    
 dropout_6 (Dropout)      (None, 64, 64, 6  0        ['conv2d_12[0][0]']        
                          4)                                                    
 conv2d_13 (Conv2D)       (None, 64, 64, 6  36928    ['dropout_6[0][0]']        
                          4)                                                    
 conv2d_transpose_2 (Conv  (None, 128, 128,  8224    ['conv2d_13[0][0]']        
 2DTranspose)              32)                                                  
 concatenate_2 (Concatena  (None, 128, 128,  0       ['conv2d_transpose_2[0][0]'
 te)                       64)                       , 'conv2d_3[0][0]']        
 conv2d_14 (Conv2D)       (None, 128, 128,  18464    ['concatenate_2[0][0]']    
                           32)                                                  
 dropout_7 (Dropout)      (None, 128, 128,  0        ['conv2d_14[0][0]']        
                           32)                                                  
 conv2d_15 (Conv2D)       (None, 128, 128,  9248     ['dropout_7[0][0]']        
                           32)                                                  
 conv2d_transpose_3 (Conv  (None, 256, 256,  2064    ['conv2d_15[0][0]']        
 2DTranspose)              16)                                                  
 concatenate_3 (Concatena  (None, 256, 256,  0       ['conv2d_transpose_3[0][0]'
 te)                       32)                       , 'conv2d_1[0][0]']        
 conv2d_16 (Conv2D)       (None, 256, 256,  4624     ['concatenate_3[0][0]']    
                           16)                                                  
 dropout_8 (Dropout)      (None, 256, 256,  0        ['conv2d_16[0][0]']        
                           16)                                                  
 conv2d_17 (Conv2D)       (None, 256, 256,  2320     ['dropout_8[0][0]']        
                           16)                                                  
 conv2d_18 (Conv2D)       (None, 256, 256,  17       ['conv2d_17[0][0]']        
                           1)                                                   
================================================================================
Total params: 1,941,105
Trainable params: 1,941,105
Non-trainable params: 0
________________________________________________________________________________

Uczenie modelu i wizualizacja wtyników¶

Trening modelu Unet na danych treningowych (train_dataset) przez 210 epok,używając zbioru walidacyjnego (vali_dataset) do oceny skuteczności modelu podczas treningu.

In [13]:
history_unet <- unet %>% fit(
 train_dataset,
 epochs = 210, #batch_size = 32
 validation_data = vali_dataset)

plot(history_unet)

Spadek wartości loss oraz wzrost metryk accuracy i IoU na wykresie historii wskazuje na skuteczne i poprawne uczenie modelu w zadaniu segmentacji obrazu. Co więcej, nie występuje tutaj ani przeuczenia, ani niedouczenie. Model osiąga coraz lepsze wyniki w przewidywaniu klas pikseli oraz w odwzorowaniu rzeczywistych obszarów na obrazie, co potwierdza jego postęp w procesie treningu. Takie zachowanie jest pożądane i wskazuje na odpowiednią adaptację modelu do danych treningowych.

In [81]:
visualize_results <- function(model, dataset, num_images) {
  par(mfrow = c(num_images, 3), mar = c(0.01,0.01,0.1,0.1)) 
  options(repr.plot.width=20, repr.plot.height=num_images * 10)  
  
  for (i in 1:num_images) {
    batch <- dataset %>% as_iterator() %>% iter_next()
    images <- batch[[1]]
    true_masks <- batch[[2]]
    pred_masks <- model %>% predict(images)
    
    # Extract one image and masks from the batch
    image <- images[i,,,]
    true_mask <- true_masks[i,,,]
    pred_mask <- pred_masks[i,,,]
    
    # Ensure true_mask and pred_mask are tensors
    true_mask <- tf$convert_to_tensor(true_mask, dtype=tf$float32)
    pred_mask <- tf$convert_to_tensor(pred_mask, dtype=tf$float32)
    
    # Expand dimensions with integer axis
    true_mask <- tf$expand_dims(true_mask, axis = as.integer(-1))
    pred_mask <- tf$expand_dims(pred_mask, axis = as.integer(-1))
    
    # Tile masks for visualization
    true_mask_rgb <- tf$tile(true_mask, tf$constant(c(1L, 1L, 1L, 3L), dtype = tf$int32))
    pred_mask_rgb <- tf$tile(pred_mask, tf$constant(c(1L, 1L, 3L), dtype = tf$int32))
    
    # Convert tensor RGB to raster objects
    true_mask_raster <- array(as.numeric(true_mask_rgb), dim=c(dim(true_mask_rgb)[1:2], 3))
    pred_mask_raster <- array(as.numeric(pred_mask_rgb), dim=c(dim(pred_mask_rgb)[1:2], 3))
    
    image <- as.raster(as.array(image))
    
    # Plot the image and masks with titles
    plot(as.raster(image), main = paste("Image", i), cex.main = 1.5)  # Increase title font size
    plot(as.raster(true_mask_raster), main = "True Mask")
    plot(as.raster(pred_mask_raster), main = "Predicted Mask")
  }
}

visualize_results(unet, vali_dataset, 5)

Po wizualizacji wyników działania segmentacji można zauważyć, że model działa prawidłowo i skutecznie wyróżnia interesujące nas obszary na obrazie. Prawidłowość działania modelu objawia się poprzez dokładne odwzorowanie granic oraz identyfikację obszarów zainteresowania, takich jak obszary zalanego czy ziemi. oprawność segmentacji potwierdza, że model nauczył się odpowiednich cech i wzorców charakterystycznych dla klasyfikacji pikseli.

In [14]:
pred = unet %>% predict(test_dataset)
unet %>% evaluate(test_dataset)
loss
0.869765818119049
iou
0.733376562595367
accuracy
0.851719975471497

Podsumowując, uzyskane wyniki metryk loss, IoU i accuracy sugerują, że model Unet działa dobrze w zadaniu segmentacji obrazu na dwie klasy. Choć wartość straty jest umiarkowana, IoU i dokładność są na dobrym poziomie, co świadczy o skuteczności modelu w identyfikowaniu oraz odwzorowywaniu istotnych obszarów na obrazach testowych.